package labox.innovation.gameserver.geodata;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import labox.innovation.config.Config;
import labox.innovation.gameserver.model.L2Object;
import labox.innovation.gameserver.model.Location;
import labox.innovation.gameserver.model.actor.instance.FuzePcInstance;
import labox.innovation.gameserver.pathfinding.Node;
import labox.innovation.gameserver.pathfinding.cellnodes.CellPathFinding;
import labox.innovation.gameserver.util.Util;
import labox.innovation.util.Point2D;
import labox.innovation.util.Point3D;

/**
 * 
 * @author -Nemesiss-
 */
public class GeoEngine implements GeoData {

	private static final Logger _log = LoggerFactory.getLogger(GeoData.class.getName());

	private short[][] _physicalLayer;

	private int widthTile, heightTile, tileWidth, tileHeight, width, height;

	public GeoEngine() {
	}

	//Public Methods
	/**
	 * @see labox.innovation.gameserver.geodata.GeoData#getType(int, int)
	 */
	@Override
	public int getType(int x, int y) {
		return getPhysicalLayer()[x >> 4][y >> 4];
	}

	/**
	 * @see labox.innovation.gameserver.geodata.GeoData#geoPosition(int, int)
	 */
	@Override
	public String geoPosition(int x, int y) {
		int gx = x >> 4;
		int gy = y >> 4;
		return "bx: " + gx + " by: " + gy;
	}

	/**
	 * @see labox.innovation.gameserver.geodata.GeoData#canSeeTarget(L2Object, Point3D)
	 */
	@Override
	public boolean canSeeTarget(L2Object cha, Point2D target) {
		return canSeeTarget(cha.getX(), cha.getY(), target.getX(), target.getY());
	}

	/**
	 * @see labox.innovation.gameserver.geodata.GeoData#canSeeTarget(labox.innovation.gameserver.model.L2Object, labox.innovation.gameserver.model.L2Object)
	 */
	@Override
	public boolean canSeeTarget(L2Object cha, L2Object target) {
		return cha.getPosition().getScene() == target.getPosition().getScene() && cha.getInstanceId() == target.getInstanceId() && canSeeTarget(cha.getX(), cha.getY(), target.getX(), target.getY());
	}

	@Override
	public boolean canSeeTarget(int x, int y, int tx, int ty) {
		return canSee(x, y, tx, ty);
	}

	private boolean canSee(int x, int y, int tx, int ty) {
		return Util.FastDistance2D(x, y, tx, ty) < 80;
	}

	/**
	 * 返回是否能够移动到目标地点
	 */
	@Override
	public boolean canMoveFromToTarget(int x, int y, int tx, int ty) {
		Location destiny = moveCheck(x, y, tx, ty);
		return destiny.getX() == tx && destiny.getY() == ty;
	}

	/**
	 * TODO 需要完成这个这函数 返回一个能够走到的坐标，给定一个出发点和目的点，算出一个能够走到的位置
	 */
	@Override
	public Location moveCheck(int x, int y, int tx, int ty) {
		if (tx < 0)
			tx = 0;
		else if (tx >= getWidth())
			tx = getWidth() - 1;
		if (ty < 0)
			ty = 0;
		else if (ty >= getHeight())
			ty = getHeight() - 1;
		int gtx = tx >> 4;
		int gty = ty >> 4;
		if (_physicalLayer[gtx][gty] == 0) {
			return new Location(x, y);
		}

		return new Location(tx, ty);
	}

	@Override
	public boolean hasGeo(int x, int y) {
		return _physicalLayer[x >> 4][y >> 4] != -1;
	}

	/**
	 * @param x
	 * @param y
	 * @param z
	 * @return NSWE: 0-15
	 */
	@Override
	public Node[] getNeighbors(Node n) {
		int nodeX, nodeY;
		int rootX = n.getLoc().getNodeX();
		int rootY = n.getLoc().getNodeY();
		Node[] result = new Node[4];
		int j = 0;
		for (int i = 1; i < 9; i += 2) {
			int dirX = i % 3 - 1;
			int dirY = i / 3 - 1;
			nodeX = rootX + dirX;

			nodeY = rootY + dirY;
			if (nodeX < 0 || nodeX >= getWidthTile() || nodeY < 0 || nodeY >= getHeightTile())
				continue;
			if (_physicalLayer[nodeX][nodeY] != 0) {
				result[j++] = CellPathFinding.getInstance().readNode(this, nodeX, nodeY);
			}
		}

		return result;
	}

	public static final GeoEngine fromXML(File fn, ByteArrayOutputStream baos) {
		if (_log.isDebugEnabled())
			_log.debug("Geo Engine: - Loading: " + fn);
		GeoEngine ge = null;
		try {
			FileInputStream fin = new FileInputStream(fn);
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(fin);
			Element root;
			root = document.getDocumentElement();
			if (root.getTagName().compareTo("map") != 0) {
				throw new Exception("Not a map file!");
			}
			ge = fromXML(root, baos);
			fin.close();
		} catch (Exception e) {
			_log.error("GeoEngine error file name:" + fn, Config.SERVER_ID, e);
		}
		return ge;
	}

	/*
	 * 4个字节为一个循环  
	 * data[4*i] 0~3 bit 第1层高位  4~7bit第0层高位
	 * data[4*i+1] 0层低位
	 * data[4*i+2] 1层低位
	 * data[4*i+3] 0~3bit 逻辑数据，4~5bit 1层的翻转情况，6~7bit 0层的翻转情况
	 * byte[][] _physicalLayer 修改为 short[][]
	 */
	private static final GeoEngine fromXML(Element e, ByteArrayOutputStream bais) throws Exception {
		GeoEngine ge = new GeoEngine();

		ge.widthTile = Integer.parseInt(e.getAttribute("width"));
		ge.heightTile = Integer.parseInt(e.getAttribute("height"));
		ge.tileWidth = Integer.parseInt(e.getAttribute("tilewidth"));
		ge.tileHeight = Integer.parseInt(e.getAttribute("tileheight"));
		ge.width = ge.widthTile * ge.tileWidth;
		ge.height = ge.heightTile * ge.tileHeight;
		NodeList nl = e.getElementsByTagName("tileset");
		TileSet[] st = new TileSet[nl.getLength()];
		int i;
		for (i = -1; ++i < nl.getLength();) {
			st[i] = TileSet.fromXML((Element) nl.item(i));
		}

		nl = e.getElementsByTagName("layer");
		MapLayer[] _layer = new MapLayer[nl.getLength()];
		for (i = -1; ++i < nl.getLength();) {
			_layer[i] = MapLayer.fromXML(ge, (Element) e.getElementsByTagName("layer").item(i));
		}
		bais.write(ge.tileWidth);
		bais.write(ge.tileHeight);
		bais.write(ge.widthTile);
		bais.write(ge.heightTile);
		bais.write(_layer.length - 1);
		int j, n;
		byte cell;
		int temp;
		for (i = -1; ++i < ge.widthTile * ge.heightTile;) {//地图tile数据
			n = 0;
			cell = 0;

			for (j = -1; ++j < _layer.length - 1;) {
				temp = _layer[j].getTiles()[2 * i];
				temp = (temp >> 8) & 0x0f;
				cell |= (temp << (j * 4));
			}

			//0~3bit 第1层的高位    4~7bit第0层高位
			bais.write(cell); //存放 第一层和第2层的高位

			for (j = -1; ++j < _layer.length - 1;) {
				bais.write(_layer[j].getTiles()[2 * i] & 0x00ff); //每层的低8位
				n |= _layer[j].getTiles()[2 * i + 1] << (j * 2);
			}
			//0~3bit 逻辑数据，4~5bit 1层的翻转情况，6~7bit 0层的翻转情况
			bais.write((_layer[_layer.length - 1].getTiles()[2 * i] << 4) | n);//逻辑层数据
		}

		bais.write(st.length);//tileset数据
		for (i = -1; ++i < st.length;) {
			bais.write(st[i].tileheight);
		}
		for (i = -1; ++i < st.length;) {
			bais.write(st[i].imageID);
		}
		//把最后一层物理层转成2维数组加快寻址
		ge._physicalLayer = new short[ge.widthTile][ge.heightTile];
		for (i = -1; ++i < ge.heightTile;) {
			for (j = -1; ++j < ge.widthTile;) {
				ge._physicalLayer[j][i] = _layer[_layer.length - 1].getTiles()[(i * ge.widthTile + j) * 2];
			}
		}
		//free memory
		for (i = -1; ++i < _layer.length;) {
			_layer[i].free();
			_layer[i] = null;
		}
		_layer = null;
		for (i = -1; ++i < st.length;) {
			st[i] = null;
		}
		st = null;

		return ge;
	}

	public int getWidthTile() {
		return widthTile;
	}

	public int getHeightTile() {
		return heightTile;
	}

	@Override
	public int getWidth() {
		return width;
	}

	@Override
	public int getHeight() {
		return height;
	}

	public int getTileWidth() {
		return tileWidth;
	}

	public int getTileHeight() {
		return tileHeight;
	}

	public short[][] getPhysicalLayer() {
		return _physicalLayer;
	}

	private static final class TileSet {

		int firstgid;

		int tileheight;

		int imageID;

		public static final TileSet fromXML(Element e) {
			TileSet ts = new TileSet();
			ts.firstgid = Integer.parseInt(e.getAttribute("firstgid"));
			ts.tileheight = Integer.parseInt(e.getAttribute("tileheight"));
			String file = ((Element) e.getElementsByTagName("image").item(0)).getAttribute("source");
			ts.imageID = Integer.parseInt(file.substring(file.lastIndexOf('_') + 1, file.lastIndexOf('.')));
			return ts;
		}
	}
}
